-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move navigation and selection logic to WritingFlow #19397
Conversation
@@ -469,18 +401,15 @@ function BlockListBlock( { | |||
); | |||
|
|||
return ( | |||
<IgnoreNestedEvents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aduth Might be of interest to you as you originally created this component.
7e7faf5
to
96fd5fb
Compare
Did you test the performance impact of this PR. It seems like it has potential for improving the numbers. |
@youknowriad I did a quick check and I think it does improve slightly, but I have to say that, on my computer, the number vary even when I test the master branch a few times in a row. 🤷♀️ |
Related (would make redundant): #14707 |
One thing this has me wondering is whether we need to start considering to bake in I guess we document this as more-or-less assumed to be part of a standard rendering hierarchy, so the expectation is already there. Furthermore, we rely on this in the top-level post editor to be able to include the post title as part of the consideration of the writing flow. |
One thing this appears to regress on is that clicking the button block appender in a column block will first select that block before allowing the inserter to be shown. This has been a source of some trouble in the past (e.g. #18329), where the intended behavior is that clicking the appender button should immediately open the inserter. Steps to reproduce:
Expected: The inserter is opened. There are end-to-end in both of those pull requests. I think it might be that the navigation block works correctly (does appear to in my testing, not sure why it's any different). The tests modified in #19135 are primarily to test against regressions to the expected behavior described in #18928. It might be that we lack a test case specifically verifying this columns (button) appender behavior. |
I generally like the direction this is taking, and am especially fond of the idea of removing the complexities surrounding Maybe this is a matter of my own unclear expectations around what this component is meant to be responsible for handling. I think it's worth clarifying in any case (ideally, though not necessarily as part of this pull request, as documentation for the component). Alternatively, it's something where we could consider splitting off some of the specific functionality into separate components. This could be relevant to my previous point about "default" behaviors, where if we don't want to bake-in all of WritingFlow, we could still split out some of the functionality to become default. In my head, I'm thinking of components like MultiSelectScrollIntoView, which itself is somewhat related to the functionality of |
On the topic of #19397 (comment) and default behaviors, I wonder if this might have any impact on whether we will be able to implement nested block editors (e.g. #14715, I think also Widgets screen might have some nested editors?). My worry is: How does selection work for those inner editors, if it's meant to be handled by WritingFlow? Does it mean there would need to be multiple WritingFlow, one for each of the BlockEditorProviders? |
I think it might be good to split I also like baking I'm sot sure how nested editors would work, and I'm sure we'd have to revise the currently selection logic and WritingFlow as well. Should you be able to select and navigate across editors? If yes, then there should probably be one |
I'll fix the remaining issue with the column inserter and attempt to split |
I don't think it's necessarily a requirement, especially considering that if each editor is responsible for maintaining its own blocks state, I don't know how a top-level WritingFlow could possibly track the selection for those blocks (since, at that level, the blocks state is not known). |
I've now moved the focus handling to the root |
@@ -72,18 +71,17 @@ function BlockList( { | |||
hasMultiSelection, | |||
enableAnimation, | |||
} = useSelect( selector, [ rootClientId ] ); | |||
const ref = useRef(); | |||
const onSelectionStart = useMultiSelection( { ref, rootClientId } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that this is no longer rendered for every block list, but rather only for the root block list.
} = useDispatch( 'core/block-editor' ); | ||
|
||
function onMouseDown( event ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic below belongs here in my opinion, as this component handles multi selection as well as navigation mode already. Perhaps we can split this is the future, but a lot of it seems tied together.
tabIndex={ -1 } | ||
// Prevent the block from being selected when the appender is | ||
// clicked. | ||
onFocus={ stopPropagation } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aduth This fixed the issue you found (#19397 (comment)). Does everything now work correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial impression: I imagine this could work, yes, considering that IgnoreNestedEvents
was in many ways a glorified stopPropagation
anyways. Will test as part of a broader re-review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! It would be nice the eventually get rid of this as well. I'll look into it sometime separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! It would be nice the eventually get rid of this as well. I'll look into it sometime separately.
Yeah, that's fair. I'm very reluctant about any stopPropagation
, but I think it's fair to say it's not really "introduced" here, since it was more-or-less the same effect previously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, same here. At least it's now explicit that propagation is stopped here. IgnoreNestedEvents
kind of made it look like it's more ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, same here. At least it's now explicit that propagation is stopped here.
IgnoreNestedEvents
kind of made it look like it's more ok.
It's a give and take, I think. One nice thing about IgnoreNestedEvents
is that it wouldn't stop all propagation, only that which it expressly cares about, thus potentially avoiding issues with other (intended) event handling further up the element hierarchy.
47a414f
to
6c6acc0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran through a number of scenarios relating to the affected code. There's a lot of potential impact here, but as best I can tell, it's all in good shape.
While I'm approving, I do think at the very least we should resolve the issue with Node.ELEMENT_NODE
vs. node.ELEMENT_NODE
.
It's a little concerning to me how much we rely on the DOM as the source of truth here with the introduction and reliance on getBlockClientId
, but I also don't think it contradicts how we've approached WritingFlow to date, and it might be worth the compromise for all the benefits/simplification it brings.
hasMultiSelection, | ||
} = useSelect( selector, [] ); | ||
const { selectBlock } = useDispatch( 'core/block-editor' ); | ||
const onSelectionStart = useMultiSelection( ref ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Thinking out loud, since it seems fine, but worth confirming) I suppose that because useMultiSelection
returns a value via useCallback
(with memoization) that this shouldn't be a performance concern in changing values in the provided value below (reference)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, it's memoized with useCallback
, so it should be fine.
getBlockParents, | ||
}; | ||
} | ||
function selector( select ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside: While the separate function declaration still catches me off guard, one thing which is nice here is demonstrating how, if you are able to move it outside the component declaration altogether, there's a safer guarantee that you don't need to provide any dependencies to the useSelect
call (since there's no longer the component scope from which to inherit dependencies).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. It's maybe a bit strange to put it outside the component, but why not if you can prevent creating an additional function on every render.
} | ||
// Allow user to escape out of a multi-selection to a singular | ||
// selection of a block via click. This is handled here since | ||
// onFocus excludes blocks involved in a multiselection, as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This was copied from BlockListBlock, but since "onFocus" doesn't exist in this file, it's a bit confusing to read now.
* @return {?string} Client ID or undefined if the node is not part of a block. | ||
*/ | ||
export function getBlockClientId( node ) { | ||
if ( node.nodeType !== node.ELEMENT_NODE ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm mistaken, this should be:
if ( node.nodeType !== node.ELEMENT_NODE ) { | |
if ( node.nodeType !== Node.ELEMENT_NODE ) { |
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType#Constants
Currently, I believe it will always be triggering the condition, since node.ELEMENT_NODE
would evaluate to undefined
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is correct. Every node instance has these properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is correct. Every node instance has these properties.
Ah, interesting. I did not know this.
(For posterity's sake: https://dom.spec.whatwg.org/#interface-node)
// Only allow selection to be started from a selected block. | ||
onMouseLeave={ isSelected ? onMouseLeave : undefined } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this related to multi-selection? And if it is, would any of it be expected to be handled in WritingFlow instead? Considering you mention it at https://github.com/WordPress/gutenberg/pull/19397/files#r363036970 as being partly responsible for handling multi-selection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right, this is for multi selection. It would be nice to move this to WritingFlow
as well. I didn't do it here to avoid a huge PR, as it seems a little more tricky to solve.
// Prevent the block from being selected when the appender is | ||
// clicked. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was actually surprised to find that in the latest version of the plugin / core version, the behavior of clicking on the inserter in the column block is such that it requires a second click to open. I don't think it's always been like this, since I distinctly remember trying to require only a single click. One potential difference now is that, while it does only require a single click, it doesn't actually select the block. I don't particularly see this as being problematic, and in some ways it's actually an ideal behavior (since the user's intent is to insert, not necessarily to select the block).
Co-Authored-By: Andrew Duthie <[email protected]>
I agree. I have some future improvements in mind around keeping the block references higher up the tree that could eliminate having to query the DOM. Not sure if and how it will work, but I generally agree that it would be nice to avoid any queries. |
One thing I've noticed about the new behavior: When inserting a Columns block and immediately proceeding to try to adjust the widths of individual columns, it can now be more difficult to select the columns, where previously it would be enough to click the button inserter, even if it wasn't the intention to actually insert a block. Granted, "Clicking the inserter" for the purpose of selecting a block is itself a bit of an awkward expectation and probably not the desired behavior, but for lack of another option, it was at least more consistent than it is currently. |
Description
This PR moves selection and navigation mode logic (event listeners) to
WritingFlow
.WritingFlow
is rerendered less often. These functions don't depend on any block data, only on selection state.IgnoreNestedEvents
component.RootContainer
component where I moved some common block logic anduseMultiSelection
(which is now only rendering once).BlockListBlock
component. The absence of the event handlers will also make it easier later on to remove thediv
wrappers.WritingFlow
a little to separate navigation and selection logic.How has this been tested?
Screenshots
Types of changes
Checklist: